package scheduler;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.MessageConsumer;
import javax.jms.Queue;
import javax.jms.Session;

import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.command.ActiveMQObjectMessage;

import environment.Application;
import environment.Environment;
import environment.PhysicalMachine;
import environment.VirtualMachine;

public class JMSAppListener  {
	
	private Connection connection;
	private MessageConsumer messageConsumer;
	
	public JMSAppListener() throws Exception
	{
		String url = "tcp://localhost:61616";
		ConnectionFactory factory = new ActiveMQConnectionFactory(url);
		connection = factory.createConnection();
		Session session = connection.createSession(false,
			Session.AUTO_ACKNOWLEDGE);
		
		connection.start();
		
		Queue queue = session.createQueue("lsdc_queue");
		messageConsumer = session.createConsumer(queue);
	}
	
	
	public void listenApps() throws Exception
	{
		while(true)
		{
			ActiveMQObjectMessage objectMessage = ((ActiveMQObjectMessage) messageConsumer.receiveNoWait());
				
			if(objectMessage == null)
				break;
				
			if(objectMessage.getObject() instanceof Application)
				addApplication((Application) objectMessage.getObject());
		}
	}
	
	public void stop() throws Exception
	{
		connection.close();
	}
	
	public static void addApplication(Application app)
	{
		/* algorithm for inserting the app - first fit */
		
		boolean isFitting = false;
		
		isFitting = tryToAddApp(app);
				
		/* change vm config */
		if(!isFitting)
			isFitting = changeVMConfig(app);
				
		/* create new vm or migrate*/
		if(!isFitting)
			isFitting = createNewVM(app);
				
		/* start new pm */
		if(!isFitting)
			isFitting = startNewPM(app);
		
		/* outsource */		
		if(!isFitting)
			addAppToFederations(app);
		else if(Environment.federations.contains(app))
		{
			// try to remove app from federations --> could be moved from the federations
			Environment.actionCounter++;
			Environment.federations.remove(app);
		}
	}
	
	public static boolean tryToAddApp(Application app)
	{		
		boolean isFitting = false;
		
		for(PhysicalMachine pm : Environment.physicalMachines)
		{	
			if(!pm.isRunning() || pm.isMaybeTerminated())
				continue;
			
			for(VirtualMachine vm : pm.getVirtualMachines())
			{				
				/* enough available resources on the vm */
				if(app.getMaxCPUs() <= vm.getFreeCPUs())
				{
					/* add app to vm */
					vm.addApplication(app);
					
					isFitting = true;
					break;
				}
			}
			if(isFitting)
				break;
		}
		
		return isFitting;
	}
	
	private static boolean changeVMConfig(Application app)
	{
		boolean isFitting = false;
		
		for(PhysicalMachine pm : Environment.physicalMachines)
		{
			if(!pm.isRunning() || pm.isMaybeTerminated())
				continue;
			
			for(VirtualMachine vm : pm.getVirtualMachines())
			{	
				// 600 max. size of a vm
				if(vm.getMaxCPUs()+app.getMaxCPUs() <= 600 && app.getMaxCPUs() <= pm.getFreeCPUs())
				{
					vm.addMaxCPUs(app.getMaxCPUs());
					vm.addApplication(app);
					
					System.out.println("\nAPPSCHEDULER: extended VM - ID: " + vm.getId());
					
					Environment.actionCounter++;
					Environment.powerConsumption += 2;
					isFitting = true;
					break;
				}
			}
			if(isFitting)
				break;
		}
		
		return isFitting;
	}
	
	private static boolean createNewVM(Application app)
	{		
		boolean isFitting = false;
		
		/* create vm with 400 cpus */
		for(PhysicalMachine pm : Environment.physicalMachines)
		{
			if(!pm.isRunning() || pm.isMaybeTerminated())
				continue;
			
			if(pm.getFreeCPUs() >= 400)
			{
				VirtualMachine newVM = new VirtualMachine(Environment.getIncrementedVMCounter(), 400);
				pm.addVirtualMachine(newVM);
				newVM.addApplication(app);
				
				System.out.println("\nAPPSCHEDULER: created new VM - ID: " + newVM.getId());
				
				Environment.powerConsumption += 10;
				Environment.actionCounter++;
				isFitting = true;
				break;
			} 
			
		}
		
		/* create vm with remaining cpus of the pm */
		if(!isFitting)
		{
			for(PhysicalMachine pm : Environment.physicalMachines)
			{
				if(!pm.isRunning() || pm.isMaybeTerminated())
					continue;
				
				if(app.getMaxCPUs() <= pm.getFreeCPUs())
				{
					VirtualMachine newVM = new VirtualMachine(Environment.getIncrementedVMCounter(), pm.getFreeCPUs());
					pm.addVirtualMachine(newVM);
					newVM.addApplication(app);
					
					System.out.println("\nAPPSCHEDULER: created new VM - ID: " + newVM.getId());
					
					Environment.powerConsumption += 10;
					Environment.actionCounter++;
					isFitting = true;
					break;
				} 						
			}
		}
		
		return isFitting;
	}
	
	private static boolean startNewPM(Application app)
	{
		boolean isFitting = false;
		
		for(PhysicalMachine pm : Environment.physicalMachines)
		{
			if(!pm.isRunning())
			{
				pm.start();
				
				VirtualMachine newVM = new VirtualMachine(Environment.getIncrementedVMCounter(), 400);
				pm.addVirtualMachine(newVM);
				newVM.addApplication(app);
				
				System.out.println("\nAPPSCHEDULER: started new PM - ID: " + pm.getId());
				System.out.println("\nAPPSCHEDULER: created new VM - ID: " + newVM.getId());
				
				Environment.powerConsumption += 50;
				Environment.actionCounter++;
				isFitting = true;
				break;
			}
				
		}
		
		return isFitting;
	}
	
	private static void addAppToFederations(Application app)
	{
		if(!Environment.federations.contains(app))
		{
			Environment.actionCounter++;
			Environment.federations.add(app);
		}
			
	}
}
